/* Driver ini adalah driver untuk GRETH + PHY. Chip PHY
 * yang digunakan adalah 88E1111 Gigabit ethernet
 */

#include <lwip/netif.h>
#include <stdbool.h>
#include <netif/grethif.h>
#include <netif/etharp.h>
#include <lwip/stats.h>
#include <lwip/sys.h>

/* 88E1111 Section */

/* Register address - 0 */
#define M88E1111_RESET               0x8000
#define M88E1111_LOOPBACK            0x4000
#define MM88E1111_SPEEDSEL            0x2000
#define M88E1111_AUTONEGO            0x1000    

/* Register address -1 */
#define M88E1111_AUTONEGO_COMPLETED  0x0020

static struct greth_info s_grethinfo;

/* address untuk tx descriptor dan rx descriptor haruslah 1 KB boundary, 
 * oleh karena itu harus dialokasikan 2*1024 lalu dipilih alamat yang 
 * paling mungkin di buffer s_txdescriptor/s_rxdescriptor yang termasuk 
 * syarat 1 KByte boundary
 */
static unsigned char s_txdescriptor[2*1024];
static unsigned char s_rxdescriptor[2*1024];

/* utility function */

static inline unsigned int 
reg_load(unsigned int address)
{
    return *((volatile unsigned int*)address);
}

static inline void 
reg_save(unsigned int address,unsigned int value)
{
    *((volatile unsigned int*)address) = value;
}

static void
grethif_arp_timer(void* arg)
{
    etharp_tmr();
    sys_timeout(ARP_TMR_INTERVAL,grethif_arp_timer,NULL);
}


/*
 * return -1 jika gagal
 */
static int
read_mii(unsigned int phyaddr,
                 unsigned int regaddr,
                 volatile greth_regs* pregs)
{
    unsigned int tmp;
    // baca nilai di register MDIO, tunggu jika masih "busy"
    do {
        tmp = reg_load((unsigned int)&pregs->mdio);
    }while(tmp & GRETH_MII_BUSY);
    tmp =   ((phyaddr & 0x1F) << 11) |
            ((regaddr & 0x1F) << 6 ) | 2;
    reg_save((unsigned int)&pregs->mdio,tmp);
    
    // tunggu hingga berhasil ditulis ke PHY
    do {
        tmp = reg_load((unsigned int)&pregs->mdio);
    }while(tmp & GRETH_MII_BUSY);
    // jika berhasil maka baca bagian datanya kembali
    if(!(tmp & GRETH_MII_INVALID))
    {
        tmp = reg_load((unsigned int)&pregs->mdio);
        return ((tmp >> 16) & 0xFFFF);
    }
    
    // failure detected
    printf("RD_MII FAIL\n");
    return -1;
}

static void
write_mii(unsigned int phyaddr,
          unsigned int regaddr,
          unsigned int data,
          greth_regs* pregs)
{
    unsigned int tmp;
    // tunggu hingga MDIO idle
    do{
        tmp = reg_load((unsigned int)&pregs->mdio);
    }while(tmp & GRETH_MII_BUSY);
    tmp =   (data & 0xFFFF) << 16 | 
            (phyaddr & 0x1F) << 11 |
            (regaddr & 0x1F) << 6 | 0x01;
            
    // tulis nilai tersebut ke PHY ( via MDIO)
    reg_save((unsigned int)&pregs->mdio,tmp);
    
    // tunggu hingga MDIO idle
    do{
        tmp = reg_load((unsigned int)&pregs->mdio);
    }while(tmp & GRETH_MII_BUSY);
}


static void* 
get_1024boundary(void* addr)
{
    int i;
    for(i=0;i<(2*1024);++i)
    {
        ((unsigned char*)addr)[i] = 0x00;
    }
    int mod = (unsigned int)addr % (1024*1024);
    if(mod==0)
        return addr;
    return (void*)((unsigned int)addr + (1024-mod));
}

/* Allocate memory aligned to the size */
static char *almalloc(int sz)
{
    char *tmp;
    int *tmp2;
    int i;
    tmp = malloc(2*sz);
    tmp = (char *) (((int)tmp+sz) & ~(sz -1));
    tmp2 = (int *)tmp;
    for (i = 0; i < 128; i++) {
            tmp2[i*2] = 0;
    }
    return(tmp);
}


static err_t 
grethif_output(struct netif *netif, 
                        struct pbuf *p,
                        struct ip_addr *ipaddr)
{
    return etharp_output(netif, ipaddr, p);
}



/** This function is called by the ARP module when it wants
 *  to send a packet on the interface. This function input
 *  the pbuf as-is on the link medium. 
 */
static err_t 
grethif_lowlevel_output(struct netif *pnetif, struct pbuf *p)
{
    struct greth_info* pgreth = (struct greth_info*)pnetif->state;
    /* baca descriptor tx untuk menentukan apakah 
     * descriptor siap melakukan pengiriman data */
    unsigned int tmp = ( pgreth->ptxd[pgreth->txpnt].ctrl >> 11) & 1;
    if(tmp)
    {
        return ERR_IF;
    }

    struct pbuf* piter = p;
    while(piter){
        // set the address descriptor based on pbuf
        pgreth->ptxd[pgreth->txpnt].addr = (unsigned int)piter->payload;
        // check apakah descriptor tx ini adalah descriptor terakhir
        if(pgreth->txpnt == 127){
            pgreth->ptxd[pgreth->txpnt].ctrl = GRETH_TXDESC_WR | GRETH_TXDESC_EN | piter->len;
            pgreth->txpnt = 0;
        }
        else{
            pgreth->ptxd[pgreth->txpnt].ctrl = GRETH_TXDESC_WR | GRETH_TXDESC_EN | piter->len;
            pgreth->txpnt++;
        }
        piter = piter->next;
    }
    return ERR_OK;
}


static struct pbuf* 
grethif_lowlevel_input(struct netif* pnetif)
{
    struct greth_info* pgreth = (struct greth_info*)pnetif->state;
    // baca descriptor rx, tentukan apakah ada data yang akan diterima
    unsigned int tmp = (pgreth->prxd[pgreth->rxpnt].ctrl >> 11) & 1;
    if(tmp)
    {
        printf("NO DATA\n");
        return (struct pbuf*)0x0;
    }
    
    // alokasikan pbuf
    int len = (pgreth->prxd[pgreth->rxpnt].ctrl & GRETH_RXDESC_LEN);
    struct pbuf* p = pbuf_alloc(PBUF_RAW,len,PBUF_POOL);
    if(!p){
        printf("CANNOT ALLOCATE %d\n",len);        
        return (struct pbuf*)0x00;
    }
    
    pgreth->prxd[pgreth->rxpnt].addr = (unsigned int)p->payload;
    if(pgreth->rxpnt == 127)
    {
        pgreth->prxd[pgreth->rxpnt].ctrl = (GRETH_RXDESC_WR | GRETH_RXDESC_EN);
        pgreth->rxpnt = 0;
    }
    else{
        pgreth->prxd[pgreth->rxpnt].ctrl = GRETH_RXDESC_EN;
        pgreth->rxpnt ++;
    }
    
    tmp = reg_load((unsigned int)&pgreth->regs->control);
    reg_save((unsigned int)&pgreth->regs->control , tmp | GRETH_RXEN);
    return p;
}


static void
grethif_low_level_init(struct netif* pnetif)
{
    struct greth_info* pgrethinf = (struct greth_info*)pnetif->state;
    unsigned int tmp = reg_load((unsigned int)&pgrethinf->regs->control);
    pgrethinf->gbit = (tmp >> 27) & 0x01;
    pgrethinf->edcl = (tmp >> 31) & 0x01;
    if(pgrethinf->edcl)
        printf("EDCL mode detected\n");
    
    if(pgrethinf->edcl == 0)
    {
        //reset the controller
        printf("reset controller\n");
        reg_save((unsigned int)&pgrethinf->regs->control,GRETH_RESET);
        do{ } 
        while(reg_load((unsigned int)&pgrethinf->regs->control) == GRETH_RESET);
        printf("reset controller done\n");
    }
    
    // Get The PHY Address
    tmp = reg_load((unsigned int)&pgrethinf->regs->mdio);
    pgrethinf->phyaddr = ((tmp >> 11) & 0x1F);
    pgrethinf->ptxd = (struct descriptor*) almalloc(1024);// get_1024boundary(s_txdescriptor);
    pgrethinf->prxd = (struct descriptor*) almalloc(1024);//get_1024boundary(s_rxdescriptor);
    // Save tx descriptor into register
    reg_save((unsigned int)&pgrethinf->regs->tx_desc_p, 
             (unsigned int) pgrethinf->ptxd);
    tmp = reg_load((unsigned int)&pgrethinf->regs->tx_desc_p);
    printf("txdesc = %p, %x\n",pgrethinf->ptxd,tmp);
    // save rx descriptor into register
    reg_save((unsigned int)&pgrethinf->regs->rx_desc_p, 
             (unsigned int) pgrethinf->prxd);
    tmp = reg_load((unsigned int)&pgrethinf->regs->rx_desc_p);
    printf("rxdesc = %p, %x\n",pgrethinf->prxd,tmp);
    pgrethinf->prxd[0].ctrl = (pgrethinf->prxd[0].ctrl|GRETH_RXDESC_EN);
    // inisialisasi semuanya ke null
    pgrethinf->txpnt = 0;
    pgrethinf->rxpnt = 0;
    pgrethinf->txchkpnt = 0;
    pgrethinf->rxchkpnt = 0;
    
    /*
     * Reset Ethernet PHY
     * Proses resetting ethernet ini via interface MDIO, (baca dokumen GRETH di GRIP
     */
    
    if (pgrethinf->edcl == 0) {
        // reset phy dan tunggu
        write_mii(pgrethinf->phyaddr,0,M88E1111_RESET,pgrethinf->regs);
        do {
            tmp = read_mii(pgrethinf->phyaddr,0,pgrethinf->regs);
        } while (tmp & M88E1111_RESET);
        /* Jika autonegotiation enabled maka tunggu hingga
         * proses autonegotiation di phy selesai */
        if (tmp & M88E1111_AUTONEGO ) {
            int i = 0;
            while (!(read_mii(pgrethinf->phyaddr,1,pgrethinf->regs) & M88E1111_AUTONEGO_COMPLETED))
            {
                ++i;
                // Jika tidak berhasil.. belum tau, keluar saja
                if (i>60000)
                    break;
            }
        }

        /* Beritahu GRETH tentang informasi2  duplex dan speed */
        tmp = read_mii(pgrethinf->phyaddr,0,pgrethinf->regs);
        int gbit;
        int speed;
        if (((tmp >> 6) & 1) && !((tmp >> 13 ) & 1)) {
            gbit = 1;
            speed = 1;
        }

        if (!((tmp >> 6) & 1) && ((tmp >> 13 ) & 1)) {
            gbit = 0;
            speed = 1;
        }
        else {
            gbit = 0;
            speed = 0;
        }

        int duplex = (tmp >> 8) & 1;

        // Ini dari contoh gaisler, bit gbit di doc tidak ditemukan
        reg_save((unsigned int)&pgrethinf->regs->control,
                 (gbit << 8 | speed << 7 | duplex << 4));
    }
    else { // MODE EDCL
        /* wait for edcl phy initialisation to finish */
        int i = 0;
        while (i < 3) {
            tmp = reg_load((unsigned int)&pgrethinf->regs->mdio);
            if ((tmp >> 3) & 1) {
                i = 0;
            } else {
                i++;
            }
        }
    }
        // set IF MAC address
        reg_save((unsigned int)&pgrethinf->regs->esa_msb,
                 (pnetif->hwaddr[0] << 8 | pnetif->hwaddr[1]));
        reg_save((unsigned int)&pgrethinf->regs->esa_lsb,
                 (pnetif->hwaddr[2] << 24) | pnetif->hwaddr[3] << 16 | pnetif->hwaddr[4]);
        
        tmp = reg_load((unsigned int)&pgrethinf->regs->control);
        reg_save((unsigned int)&pgrethinf->regs->control,tmp|0x20);
    
}

inline int 
grethif_checkrx(int *size, struct netif* pnetif) 
{
    struct greth_info* greth = (struct greth_info*)pnetif->state;
    unsigned int tmp;
    tmp = greth->prxd[greth->rxchkpnt].ctrl;
    if (!((tmp >> 11) & 1)) {
        printf("%8.8X\n",tmp);
        *size = tmp & GRETH_RXDESC_LEN;
        if (greth->rxchkpnt == 127) {
            greth->rxchkpnt = 0;
        } else {
            greth->rxchkpnt++;
        }
        return 1;
    }
    return 0;
}


void
grethif_poll_in(struct netif* pnetif)
{
    struct pbuf* p =  grethif_lowlevel_input(pnetif);
    if(p){
        printf("%p\n");
        pbuf_free(p);
        p = NULL;
    }
    
//     int pktlen;
//     int ret = grethif_checkrx(&pktlen,pnetif);
//     if(ret==1){
//         printf("rcv: %d\n",pktlen);
//     }
    
}


err_t 
grethif_init(struct netif* pnetif)
{
    printf("greth init\n");
    s_grethinfo.regs = (greth_regs*)GRETH_ADDR;
    pnetif->state = &s_grethinfo;
    pnetif->name[0] = 'e';
    pnetif->name[1] = 't';
    pnetif->output      = &grethif_output;
    pnetif->linkoutput  = &grethif_lowlevel_output;
    s_grethinfo.ethaddr = (struct ethaddr*) &pnetif->hwaddr[0];
    grethif_low_level_init(pnetif);
    etharp_init();
    sys_timeout(ARP_TMR_INTERVAL, grethif_arp_timer, NULL);    
    printf("greth init finished\n");
    return ERR_OK;
    
}


extern int 
grethif_get_linkstatus(struct netif* pnetif)
{
    int tmp;
    struct greth_info* pinfo = (struct greth_info*)pnetif->state;
    tmp = read_mii(pinfo->phyaddr,1,pinfo->regs);
    return ((tmp >> 2) & 1);
}

extern void
grethif_display_phy(struct netif* pnetif)
{
    struct greth_info* pgrethinf = (struct greth_info*)pnetif->state;
    unsigned int tmp;
    // Get PHY status
    tmp = read_mii(pgrethinf->phyaddr,1,pgrethinf->regs);
    printf("Jabber Detect = %d\n",(tmp >> 1) & 1);
    printf("Link status = %d\n",(tmp >> 2) & 1);
    printf("Autonego = %d\n",(tmp >> 3) & 1);
    printf("Autonego status = %d\n",(tmp >> 5) & 1);
    printf("MF preamble suppres = %d\n",(tmp >> 6) & 1);
    printf("Extended status = %d\n",(tmp >> 8) & 1);
    printf("10Mbps halfduplex ability = %d\n",(tmp >> 11) & 1);
    printf("10Mbps fullduplex ability = %d\n",(tmp >> 12) & 1);
    printf("100BaseX halfduplex ability = %d\n",(tmp >> 13) & 1);
    printf("100BaseX fullduplex ability = %d\n",(tmp >> 14) & 1);    
    // Get PHY identifier
    tmp = read_mii(pgrethinf->phyaddr,3,pgrethinf->regs);
    printf("Rev Num = %X\n",(tmp & 0xF));
    printf("Model = %X\n",(tmp >> 4) & 0x3F);
    // Link partner ability
    tmp = read_mii(pgrethinf->phyaddr,5,pgrethinf->regs);
    printf("Link remote fault = %d\n",(tmp >> 13) & 1);
    printf("Link acknowledge = %d\n",(tmp >> 14) & 1);
    
    // Phy specific
    tmp = read_mii(pgrethinf->phyaddr,17,pgrethinf->regs);
    printf("Link (realtime) = %d\n",(tmp >> 10) & 1);
    
    

}

